{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fc118b68-6776-4504-ace0-0e076616fde6",
   "metadata": {},
   "source": [
    "# Time-profiling Data Science code using `cProfile`\n",
    "\n",
    "## Dr. Tirthajyoti Sarkar\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "98192c2c-f90d-4a7b-b3b2-ae23870d6f4e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         3 function calls in 0.064 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.064    0.064    0.064    0.064 <string>:1(<module>)\n",
      "        1    0.000    0.000    0.064    0.064 {built-in method builtins.exec}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cProfile\n",
    "\n",
    "SIZE = 10_000_000\n",
    "a = np.arange(SIZE)\n",
    "b = np.random.normal(size=SIZE)\n",
    "\n",
    "cProfile.run('a+b')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9500a64c-9d19-4e05-97cf-231ab5f1842e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SIZE = 10_000_000\n",
      "a = np.arange(SIZE)\n",
      "b = np.random.normal(size=SIZE)\n",
      "a+b\n"
     ]
    }
   ],
   "source": [
    "code = \"\"\"SIZE = 10_000_000\n",
    "a = np.arange(SIZE)\n",
    "b = np.random.normal(size=SIZE)\n",
    "a+b\"\"\"\n",
    "\n",
    "print(code)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6d810ec6-245d-4082-8f64-d5c16b162936",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         5 function calls in 0.488 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.078    0.078    0.488    0.488 <string>:1(<module>)\n",
      "        1    0.000    0.000    0.488    0.488 {built-in method builtins.exec}\n",
      "        1    0.028    0.028    0.028    0.028 {built-in method numpy.arange}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "        1    0.381    0.381    0.381    0.381 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "cProfile.run(code)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "8a5bf74f-fa5b-4997-a864-82a6d9503216",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         6 function calls in 0.531 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.055    0.055    0.510    0.510 1735574101.py:1(add)\n",
      "        1    0.021    0.021    0.531    0.531 <string>:1(<module>)\n",
      "        1    0.000    0.000    0.531    0.531 {built-in method builtins.exec}\n",
      "        1    0.057    0.057    0.057    0.057 {built-in method numpy.arange}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "        1    0.397    0.397    0.397    0.397 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "def add():\n",
    "    SIZE = 10_000_000\n",
    "    a = np.arange(SIZE)\n",
    "    b = np.random.normal(size=SIZE)\n",
    "    c=a+b\n",
    "\n",
    "cProfile.run('add()')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "f899c334-2d1f-41ef-aceb-71348e5a6818",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(size):\n",
    "    a = np.arange(size)\n",
    "    b = np.random.normal(size=size)\n",
    "    c=a+b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cdf8d43a-f240-4de6-9a92-6dee6c1cd98b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         6 function calls in 0.500 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.054    0.054    0.478    0.478 1565836920.py:1(add)\n",
      "        1    0.021    0.021    0.500    0.500 <string>:1(<module>)\n",
      "        1    0.000    0.000    0.500    0.500 {built-in method builtins.exec}\n",
      "        1    0.030    0.030    0.030    0.030 {built-in method numpy.arange}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "        1    0.394    0.394    0.394    0.394 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "SIZE = 10_000_000\n",
    "cProfile.run('add(SIZE)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8d038a76-4394-472b-9949-be9ef936dca5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         6 function calls in 1.034 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.114    0.114    0.987    0.987 1565836920.py:1(add)\n",
      "        1    0.047    0.047    1.034    1.034 <string>:1(<module>)\n",
      "        1    0.000    0.000    1.034    1.034 {built-in method builtins.exec}\n",
      "        1    0.082    0.082    0.082    0.082 {built-in method numpy.arange}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "        1    0.791    0.791    0.791    0.791 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "SIZE = 20_000_000\n",
    "cProfile.run('add(SIZE)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f14c476d-cc19-4ce5-b74e-abf211eabc7a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def ops(a,b):\n",
    "    x1 = a+b\n",
    "    x2 = a-b\n",
    "    x3 = a*b\n",
    "    x4 = a/b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "771a1452-2d4a-476e-8775-3479beac3700",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         4 function calls in 0.287 seconds\n",
      "\n",
      "   Ordered by: standard name\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        1    0.251    0.251    0.251    0.251 3200973052.py:1(ops)\n",
      "        1    0.036    0.036    0.286    0.286 <string>:1(<module>)\n",
      "        1    0.000    0.000    0.287    0.287 {built-in method builtins.exec}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "cProfile.run('ops(a,b)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "5af6602e-a66f-4bf0-9d22-bc5b71e8c4d0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total function calls: 48\n",
      "Total time (seconds): 1.1839559\n"
     ]
    }
   ],
   "source": [
    "import cProfile, pstats\n",
    "\n",
    "profiler = cProfile.Profile()\n",
    "# Enable profiler\n",
    "profiler.enable()\n",
    "# Function execution\n",
    "add(SIZE)\n",
    "# Disable profiler\n",
    "profiler.disable()\n",
    "# pstats\n",
    "stats = pstats.Stats(profiler)\n",
    "# Print the total time and function calls\n",
    "print(\"Total function calls:\", stats.total_calls)\n",
    "print(\"Total time (seconds):\", stats.total_tt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "91f2d625-37bf-41f6-9c92-ae52bf608d02",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "         48 function calls in 1.184 seconds\n",
      "\n",
      "   Random listing order was used\n",
      "\n",
      "   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:86(__init__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:112(__enter__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:121(__exit__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:242(helper)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\traitlets\\traitlets.py:535(get)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\traitlets\\traitlets.py:566(__get__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\utils\\ipstruct.py:125(__getattr__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\codeop.py:142(__call__)\n",
      "        4    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\compilerop.py:166(extra_flags)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:1286(user_global_ns)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3354(compare)\n",
      "        2    0.000    0.000    1.184    0.592 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3416(run_code)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\hooks.py:103(__call__)\n",
      "        2    0.000    0.000    0.000    0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\hooks.py:168(pre_run_code_hook)\n",
      "        1    0.000    0.000    0.000    0.000 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/3775033682.py:9(<module>)\n",
      "        1    0.044    0.044    1.184    1.184 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/3775033682.py:7(<module>)\n",
      "        1    0.114    0.114    1.140    1.140 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/1565836920.py:1(add)\n",
      "        1    0.081    0.081    0.081    0.081 {built-in method numpy.arange}\n",
      "        1    0.945    0.945    0.945    0.945 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n",
      "        2    0.000    0.000    0.000    0.000 {built-in method builtins.compile}\n",
      "        2    0.000    0.000    1.184    0.592 {built-in method builtins.exec}\n",
      "        4    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}\n",
      "        4    0.000    0.000    0.000    0.000 {built-in method builtins.next}\n",
      "        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}\n",
      "\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<pstats.Stats at 0x219d3a001f0>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats = pstats.Stats(profiler)\n",
    "stats.print_stats()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "06fa3a62-5fef-4d9e-8c6a-d9e309d80e16",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pstats.Stats"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(stats)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cefecaa0-9933-49ae-b0dc-3f869e83fa89",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.1839559"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats.total_tt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "fe15e37e-d329-4168-a479-60c219126e16",
   "metadata": {},
   "outputs": [],
   "source": [
    "stats.fcn_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "15c966eb-e094-4e5f-8e54-1a2c3923f6c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "560a8a4d-84fc-43c2-a1c8-79113dcc73ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "size = [int(i*1e6) for i in range(5,26,5)]\n",
    "total_tt = []\n",
    "for s in size:\n",
    "    profiler = cProfile.Profile()\n",
    "    profiler.enable()\n",
    "    add(s)\n",
    "    profiler.disable()\n",
    "    stats = pstats.Stats(profiler)\n",
    "    total_tt.append(round(stats.total_tt,3))       "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "264f4589-e40b-4c2d-bb08-9116dc4eb2e4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0.274, 0.464, 0.706, 0.94, 1.187]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "total_tt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "695c9306-bcc6-45ea-8939-28e8e127eec3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(6,3),dpi=120)\n",
    "plt.bar(x=[str(i)+'-million' for i in range(5,26,5)],\n",
    "        height=total_tt, \n",
    "        edgecolor='k',\n",
    "        color=\"#2c75b0\")\n",
    "plt.xlabel(\"Array size\", fontsize=16)\n",
    "plt.ylabel(\"Time taken (seconds)\",fontsize=16)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b1e4af3-385a-420a-be85-449119210f93",
   "metadata": {},
   "source": [
    "## Data science workflow profiling\n",
    "\n",
    "While measuring the execution time of these small standalone functions serve as basic demonstration of the usage of these profilers, the real utility is realized when they are used in a large-scale data science workflow. Such a workflow has a variety of modules and functions and we can set up profiling for all of them if necessary. The output may be logged into a database or even be fed into a monitoring system that will track the performance of the modules over time and take action if needed (e.g., a function has poorly performed and took too much time in a certain run or for a certain input data).\n",
    "\n",
    "![ds-workflow](https://raw.githubusercontent.com/tirthajyoti/Machine-Learning-with-Python/master/Time-profiling/8.14%20-%20profile-DS-workflow.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
